package ar.uba.fi.algo3.tp3.modelo;

import ar.uba.fi.algo3.tp3.controlador.ControladorPacman;
import ar.uba.fi.algo3.tp3.controlador.ObservadorTecladoPacman;
import ar.uba.fi.algo3.tp3.controlador.TecladoObservador;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Partida {

    private static Partida instance;
    private InfoPartida infoPartida;
    private Mapa mapa;
    private ControladorPacman controladorPacman;
    private List<ObservadorPuntos> listaObservadoresPuntos;
    private List<ObservadorVidas> listaObservadoresVidas;
    private boolean perdida;

    private Partida() {
        this.infoPartida             = new InfoPartida();
        this.controladorPacman       = ControladorPacman.getInstance();
        this.listaObservadoresPuntos = new ArrayList<ObservadorPuntos>();
        this.listaObservadoresVidas  = new ArrayList<ObservadorVidas>();
        this.perdida                 = false;
        this.crearMapa();
    }

    public boolean isPerdida() {
        return perdida;
    }

    public static Partida getInstance() {
        if (instance == null) {
            instance = new Partida();
        }
        return instance;
    }

    private void limpiarPartida() {
        controladorPacman.quitarDibujables();
        controladorPacman.quitarObjetosVivos();
    }

    public void inicializarPartida() {
        this.limpiarPartida();
        GeneradorElementosDePartida generador = new GeneradorElementosDePartida(mapa, controladorPacman);
        this.agregarObjetosVivos();
        Pacman pacman = this.mapa.getPacman();
        generador.inicializarPartidaGenerico();
        generador.generarVistaPacman(pacman);
        this.agregarObservadorPacman(pacman);
    }

    public void inicializarPartidaDesdeMapa(Mapa mapa) {
        this.limpiarPartida();
        GeneradorElementosDePartida generador = new GeneradorElementosDePartida(mapa, controladorPacman);
        generador.inicializarPartidaGenerico();
        Pacman pacman = mapa.getPacman(); 
        generador.generarVistaPacman(pacman);
        this.agregarObservadorPacman(pacman);
    }    

    public void pasarNivel() {
        controladorPacman.detenerJuego();
        infoPartida.incrementarNivel();
        crearMapa();
        inicializarPartida();
        controladorPacman.comenzarJuego();
    }

    public void setNivel(int nivel) {
        infoPartida.setNivel(nivel);
    }

    public void removerPastilla(Pastilla p) {
        this.controladorPacman.removerDibujableEnBaseAPosicionable(p);
        this.mapa.quitarPastilla(p);
        List<Pastilla> listaPastillas = mapa.getListaPastillas();
        if (listaPastillas.isEmpty()) {
            pasarNivel();
        }
    }

    public void removerFruta(Fruta f) {
        this.controladorPacman.removerDibujableEnBaseAPosicionable(f);
    }

    public Mapa getMapa() {
        return this.mapa;
    }

    public void crearMapa() {
        this.mapa = new Mapa();
        this.mapa.inicializarMapa(infoPartida.getNivel());
    }

    public int getPuntos() {
        return infoPartida.getPuntos();
    }

    public void restarVidas() {
        infoPartida.decrementarVidas();
        notificarObservadoresVidas();
        if (infoPartida.getVidas() == 0) {
            ControladorPacman.getInstance().forzarPausa(true);
            this.perdida = true;
        }
    }

    public void sumarPuntos(int puntos) {
        infoPartida.sumarPuntos(puntos);
        notificarObservadoresPuntos();
    }

    private void notificarObservadoresVidas() {
        for (Iterator<ObservadorVidas> it = listaObservadoresVidas.iterator(); it.hasNext();) {
            ObservadorVidas observadorVidas = it.next();
            observadorVidas.cambioVidas(infoPartida.getVidas());
        }
    }

    private void notificarObservadoresPuntos() {
        for (Iterator<ObservadorPuntos> it = listaObservadoresPuntos.iterator(); it.hasNext();) {
            ObservadorPuntos observadorPuntos = it.next();
            observadorPuntos.cambioEnPuntos(infoPartida.getPuntos());
        }
    }

    public void agregarObservadorPuntos(ObservadorPuntos op) {
        listaObservadoresPuntos.add(op);
    }

    public void agregarObservadorVidas(ObservadorVidas op) {
        listaObservadoresVidas.add(op);
    }

    public void resetearCuentaFantasmasComidos() {
        infoPartida.resetearFantasmasComidos();
    }

    public void sumarPuntosFantasma() {
        this.sumarPuntos((int) (200 * (Math.pow(2, infoPartida.getFantasmasComidosAlHilo()))));
        infoPartida.incrementarFantasmasComidos();
    }

    public InfoPartida getInfoPartida() {
        return this.infoPartida;
    }

    public void setInfoPartida(InfoPartida ip) {
        this.perdida = false;
        this.infoPartida = ip;
        notificarObservadoresPuntos();
        notificarObservadoresVidas();
    }

    public void nuevaPartida() {
        try {
            this.controladorPacman.forzarPausa(true);
            Thread.sleep(100);
            this.setInfoPartida(new InfoPartida());
            crearMapa();
            this.inicializarPartida();
            this.controladorPacman.forzarPausa(false);
        } catch (Exception e) {
            controladorPacman.informarDeError("Hubo un problema al intentar crear una nueva partida");
        }
    }

    public void setMapa(Mapa mapa) {
        try {
            this.controladorPacman.forzarPausa(true);
            Thread.sleep(100);
            this.mapa = mapa;
            this.inicializarPartidaDesdeMapa(mapa);
            this.controladorPacman.forzarPausa(false);
        } catch (Exception e) {
            controladorPacman.informarDeError("Hubo un problema al intentar cargar un mapa");
        }
    }

    private void agregarObjetosVivos() {
        Pacman pacman = new Pacman(mapa);
        Fruta fruta  = new Fruta(-30, 700, mapa);
        mapa.agregarFruta(fruta);
        mapa.establecerPacman(pacman);
        
        mapa.agregarFantasma(new FantasmaCeleste(mapa, infoPartida.getNivel()));
        mapa.agregarFantasma(new FantasmaRosa(mapa, infoPartida.getNivel()));
        mapa.agregarFantasma(new FantasmaRojo(mapa, infoPartida.getNivel()));
        mapa.agregarFantasma(new FantasmaAmarillo(mapa, infoPartida.getNivel()));
    }

    private void agregarObservadorPacman(Pacman pacman) {
        ObservadorTecladoPacman otp = new ObservadorTecladoPacman(pacman);
        this.agregarObservadorTeclado(otp);
    }

    private void agregarObservadorTeclado(TecladoObservador observador) {
        controladorPacman.agregarTecladoObservador(observador);
    }
}